package com.ymx.darling.session.defaults;

import com.ymx.darling.bean.entity.BeanInfo;
import com.ymx.darling.bean.entity.BeanInfoManager;
import com.ymx.darling.bean.entity.FieldInfo;
import com.ymx.darling.dialect.keygenerator.AssignKeyGenerator;
import com.ymx.darling.bean.entity.id.IdType;
import com.ymx.darling.dialect.keygenerator.KeyGenerator;
import com.ymx.darling.dialect.keygenerator.NoneKeyGenerator;
import com.ymx.darling.bean.sql.BoundSql;
import com.ymx.darling.bean.sql.SqlCommandType;
import com.ymx.darling.bean.sql.SqlParsingHelper;
import com.ymx.darling.bean.sql.Statement;
import com.ymx.darling.build.Configuration;
import com.ymx.darling.build.mapper.dynamic.SqlExecutionType;
import com.ymx.darling.build.mapper.dynamic.SqlSource;
import com.ymx.darling.build.mapper.dynamic.SqlSourceRepository;
import com.ymx.darling.build.mapper.dynamic.StaticSqlSource;
import com.ymx.darling.build.mapper.dynamic.result.ResultMapNode;
import com.ymx.darling.cursor.Cursor;
import com.ymx.darling.dialect.Dialect;
import com.ymx.darling.dialect.DialectManager;
import com.ymx.darling.dialect.page.Page;
import com.ymx.darling.dialect.page.PageBody;
import com.ymx.darling.dialect.page.PageMpl;
import com.ymx.darling.exception.*;
import com.ymx.darling.executor.Executor;
import com.ymx.darling.log.LogFactory;
import com.ymx.darling.session.Session;
import com.ymx.darling.session.map.MapConvertHandler;
import com.ymx.darling.session.map.MapConvertHandlerMpl;
import com.ymx.darling.session.setting.Callable;
import com.ymx.darling.session.setting.Context;
import com.ymx.darling.session.setting.relation.RelationEntity;
import com.ymx.darling.session.setting.relation.RelationEntityBuilder;
import com.ymx.darling.session.setting.relation.RelationSelector;
import com.ymx.darling.util.ArrayUtil;
import com.ymx.darling.util.StringUtil;

import java.sql.Connection;
import java.util.*;

/**
 * 默认的Session实现类
 * @author 爱Java的小于
 */
public class DefaultSession implements Session {
    /**
     * sql执行器
     */
    private final Executor executor;
    /**
     * 全局配置实例
     */
    private final Configuration configuration;
    /**
     * 保存所有游标实例
     */
    private List<Cursor<?>> cursors;

    public DefaultSession(Configuration configuration, boolean autoCommit) {
        this.configuration = configuration;
        this.executor = configuration.createExecutor(autoCommit);
    }

    @Override
    public <E> E selectOne(Class<?> module, String sql, Object... param) {
        List<E> tList = this.selectList(module, sql, param);
        if (tList.isEmpty()) {
            return null;
        } else {
            if (tList.size() > 1) {
                throw new DarlingException("不能将多行记录映射为一个对象,错误的结果集行数: " + tList.size());
            } else {
                return tList.get(0);
            }
        }
    }


    /**
     * 查询一个List列表并返回
     * @param module 结果集映射类型
     * @param sql sql语句
     * @param param 占位符参数
     * @param <E> E
     * @return List
     */
    public <E> List<E> selectList(Class<?> module, String sql, Object... param){
        try {
            if (Objects.isNull(module)) {
                throw new DarlingException("未指定查询结果映射类型");
            } else {
                Statement statement = this.getStatement(module, SqlExecutionType.QUERY, sql, param);
                return this.executor.queryList(statement);
            }
        } finally {
            this.clearContextSetting();
        }
    }


    /**
     * 查询集合类型为Map的结果集
     *
     * @param sql sql
     * @param module 集合内元素的类型
     * @param key 指定哪个为key的字段
     * @param param sql参数列表
     * @param <K> K
     * @param <E> E
     * @return Map
     */
    @Override
    public <K,E> Map<K, E> selectMap(Class<?> module, String sql, String key, Object... param) {
        try {
            if (!(Map.class == module || BeanInfoManager.isRegister(module))) {
                throw new DarlingException(String.format("错误的映射类型'%s':当以Map集合返回多行数据时只能将数据行映射为Map类型 或 被@Entity标注的自定义类型", module));
            } else {
                List<E> list = this.selectList(module, sql, param);
                if (list.isEmpty()) {
                    return new HashMap<>();
                } else { //开始执行转换
                    MapConvertHandler convertHandler = new MapConvertHandlerMpl();
                    if (module == Map.class) { //判断待转换的集合元素类型为Map 将多个Map放入Map集合中
                        return convertHandler.convertToMapFromList(list, key);
                    } else { //将实体类放入Map中
                        BeanInfo beanInfo = BeanInfoManager.getBeanInfo(module);
                        Map<String, FieldInfo> fieldMap = beanInfo.getSimpleColumnMap();
                        FieldInfo keyFieldInfo = fieldMap.get(key);
                        if (Objects.nonNull(keyFieldInfo)) {
                            return convertHandler.convertToMapFromListByFieldName(list, keyFieldInfo);
                        } else {
                            throw new EntityException(String.format("字段'%s'在类'%s'中不存在", key, module));
                        }
                    }
                }
            }
        } finally {
            this.clearContextSetting();
        }
    }


    @Override
    public <T> Cursor<T> selectCursor(Class<?> type, int faceSize, String sql, Object... param) {
        try {
            if (type == null) {
                throw new DarlingException("执行游标查询时未指定结果映射类型");
            } else {
                Statement statement = this.getStatement(type, SqlExecutionType.QUERY, sql, param);
                statement.setFaceSize(faceSize);
                Cursor<T> cursor = this.executor.queryCursor(statement);
                this.registerCursor(cursor);
                return cursor;
            }
        } finally {
            this.clearContextSetting();
        }
    }


    @Override
    public <T> Page<T> selectPage(Class<?> type, String sql, Object... param) {
        try {
            if (!Context.hasPageBody()) { //判断没有为当前分页查询配置分页数据 抛出异常
                throw new DarlingException("未配置分页数据");
            } else {
                // 查询前获取当前缓存命名空间 进行暂存 m
                Class<?> cacheNamespace = Context.getNamespace();
                PageBody pageBody = Context.getPageBody();
                PageMpl<T> page = new PageMpl<>();
                page.setPageNum(pageBody.getPageNum()); // 第几页
                page.setPageSize(pageBody.getPageSize()); // 页的大小
                // 查询count总数
                Context.setPage(null); // 总数查询就不需要设置分页参数
                Integer count = this.selectCount(sql, param);
                page.setCount(count);
                if (count > 0) {
                    Context.setNamespace(cacheNamespace); // 重置缓存命名空间
                    Context.setPage(pageBody); // 重新添加分页参数
                    page.setValueList(this.selectList(type, sql, param)); // 查询分页数据列表
                } else {
                    page.setValueList(new ArrayList<>());
                }

                return page;
            }
        } finally {
            this.clearContextSetting();
        }
    }


    /**
     * 根据sql统计查询行数
     * @param sql sql
     * @param params 参数
     * @return Integer 查询到的行数
     */
    private Integer selectCount(String sql, Object... params) {
        // 构建count的sql
        String selectCountSql = String.format("SELECT COUNT(*) FROM (%s) tb", sql);
        // 返回查询的count结果
        return this.selectOne(Integer.class, selectCountSql, params);
    }


    @Override
    public int update(String sql, Object... param) {
        try {
            return this.executor.update(this.getStatement(null, SqlExecutionType.UPDATE, sql, param));
        } finally {
            this.clearContextSetting();
        }
    }

    @Override
    public int updateCall(Callable callable) {
        Statement statement = this.getStatement(null, SqlExecutionType.UPDATE, null);
        callable.addCallableSetting(statement);
        return this.executor.update(statement);
    }

    @Override
    public <T> List<T> selectCall(Callable callable) {
        Statement statement = this.getStatement(Map.class, SqlExecutionType.QUERY, null);
        callable.addCallableSetting(statement);
        return this.executor.queryList(statement);
    }


    @Override
    public <T> T getMapper(Class<T> mapperInterface) {
        return this.configuration.getMapper(mapperInterface,this);
    }


    @Override
    public Configuration getConfiguration() {
        return this.configuration;
    }


    /**
     * 清除上下文信息
     */
    private void clearContextSetting() {
        // 没有基于接口代理开发时清除上下文信息
        if (!Context.hasMapperMethod()) {
            Context.clearSetting();
        }
    }


    /**
     * 设置普通查询和游标查询以及更新sql的通用设置
     * @param module 结果集映射类型
     * @param statement sql语句
     * @param params sql占位符参数
     * @return 返回sql的包装类
     */
    private Statement getStatement(Class<?> module, SqlExecutionType executionType, String statement, Object... params) {
        // 构建Statement并保存基础信息
        Statement sqlStatement = new Statement();
        sqlStatement.setModule(module);
        sqlStatement.setExecutor(this.executor);
        sqlStatement.setConfiguration(this.configuration);
        // 设置其他配置项
        this.addSettings(sqlStatement, executionType, statement, params);

        return sqlStatement;
    }


    /**
     * 为statement添加查询设置
     * @param sqlStatement sql封装实例
     */
    private void addSettings(Statement sqlStatement, SqlExecutionType executionType, String statement, Object[] params) {
        // 设置命名空间
        if (Context.hasNamespace()) {
            sqlStatement.setNamespace(Context.getNamespace());
        } else {
            sqlStatement.setNamespace(this.getClass());
        }

        // 设置日志
        if (Context.hasStatementLog()) {
            sqlStatement.setLogger(Context.getStatementLog());
        } else {
            sqlStatement.setLogger(LogFactory.getLog(sqlStatement.getNamespace()));
        }

        // 设置boundSql
        BoundSql boundSql;
        // 根据id从SqlSourceRepository中获取SqlSource 并解析BoundSql
        SqlSourceRepository sqlSourceRepository = configuration.getSqlSourceRepository();
        if (sqlSourceRepository.hasSqlNode(statement)) {
            Context.setSqlStatementId(statement);
            sqlStatement.setStatementId(statement);
            SqlSource sqlSource = sqlSourceRepository.getSqlSource(statement);
            boundSql = sqlSource.getBoundSql(SqlParsingHelper.formatParameter(params));
            // 设置是否自定义映射
            if (StringUtil.isNoNull(sqlSource.getResultMap())) {
                String resultMapId = sqlSource.getResultMap();
                ResultMapNode resultMapNode = sqlSourceRepository.getResultMap(sqlStatement.getNamespace(), resultMapId);
                if (resultMapNode == null) {
                    throw ExceptionFactory.wrapException("设置ResultMap映射器时抛出异常", new ResultMappingException("找不到id为'"+resultMapId+"'的resultMap映射器"));
                }
                sqlStatement.setResultMapNode(resultMapNode);
            }
        // 根据sql语句构建BoundSql
        } else {
            // 当前调用为非mapper方法调用时将sql语句作为statementId
            String statementId = StringUtil.createStatementIdWithMapperMethod();
            if (statementId == null) statementId = statement;

            // 将当前sql的id放入上下文
            Context.setSqlStatementId(statementId);
            sqlStatement.setStatementId(statementId);

            // 判断为原生sql(语句中包含?)
            if (SqlParsingHelper.containsQuestionMark(statement)) {
                boundSql = new BoundSql(statement, ArrayUtil.asList(params));
            } else {
                StaticSqlSource staticSqlSource = new StaticSqlSource(null, null, statement);
                boundSql = staticSqlSource.getBoundSql(SqlParsingHelper.formatParameter(params));
            }
        }
        // 创建BoundSql实例
        sqlStatement.setBoundSql(boundSql);

        // 设置sql语句的类型
        if (executionType == SqlExecutionType.QUERY) {
            sqlStatement.setSqlCommandType(SqlCommandType.SELECT);
        } else {
            SqlCommandType sqlCommandType = SqlParsingHelper.getSqlCommandType(sqlStatement.getStatementId(), boundSql.getSql());
            sqlStatement.setSqlCommandType(sqlCommandType);
            // 判断为Insert语句则判断是否为批量插入
            if (sqlCommandType == SqlCommandType.INSERT) {
                Object parameterObject = boundSql.getParameterObject();
                Class<?> paramType; // 入参参数类型
                // 判断批量插入
                if (Context.hasInsertBatchSize()) {
                    if (!(parameterObject instanceof List)) {
                        throw new DarlingException(String.format("非法的批量插入类型'%s'. 进行批量插入时必须使用List保存多个实体", parameterObject.getClass().getName()));
                    }
                    List<?> entityList = (List<?>) parameterObject;
                    if (entityList.isEmpty()) {
                        throw new DarlingException("批量插入的实体列表不能为空");
                    }
                    paramType = entityList.get(0).getClass();
                    sqlStatement.setInsertBatchSize(Context.getInsertBatchSize());
                } else {
                    paramType = parameterObject.getClass();
                }
                // 设置主键生成器
                if (BeanInfoManager.isRegister(paramType)) {
                    BeanInfo beanInfo = BeanInfoManager.getBeanInfo(paramType);
                    KeyGenerator keyGenerator = this.getKeyGenerator(beanInfo);
                    sqlStatement.setKeyGenerator(keyGenerator);
                }
            }
        }
        if (sqlStatement.getKeyGenerator() == null) {
            sqlStatement.setKeyGenerator(new NoneKeyGenerator());
        }

        // 以下配置针对查询操作
        if (sqlStatement.getModule() != null) {
            if (Context.hasNestedParam()) {
                sqlStatement.setNestedQueryParam(Context.getNestedParam());
            }
            if (Context.hasPageBody()) {
                PageBody pageBody = Context.getPageBody();
                int pageNum = pageBody.getPageNum();
                int pageSize = pageBody.getPageSize();
                Dialect dialect = DialectManager.getDialect();
                dialect.paginate(boundSql, pageNum, pageSize);
            }
            if (Context.hasRelationSelector()) {
                RelationSelector relationSelector = Context.getRelationSelector();
                RelationEntityBuilder relationEntityBuilder = new RelationEntityBuilder();
                RelationEntity relationEntity = relationEntityBuilder.createRelationEntity(sqlStatement.getModule(), relationSelector.getRelationNodeList());
                sqlStatement.setRelationEntity(relationEntity);
            }
        }
    }


    /**
     * 返回主键生成器
     * @param beanInfo 当前插入实体的BeanInfo
     * @return KeyGenerator
     */
    private KeyGenerator getKeyGenerator(BeanInfo beanInfo) {
        FieldInfo idColumnInfo = beanInfo.getIdColumnInfo();
        KeyGenerator keyGenerator;
        if (idColumnInfo != null) {
            if (idColumnInfo.getIdType() == IdType.AUTO || idColumnInfo.getIdType() == IdType.SEQUENCE) {
                keyGenerator = DialectManager.getDialect().getKeyGenerator(); // 获取数据库方言中的主键生成器
            } else if (idColumnInfo.getIdType() == IdType.ASSIGN) {
                keyGenerator = new AssignKeyGenerator();
            } else {
                keyGenerator = new NoneKeyGenerator();
            }
        } else {
            keyGenerator = new NoneKeyGenerator();
        }
        return keyGenerator;
    }


    /**
     * 注册游标实例
     * @param cursor cursor
     */
    private void registerCursor(Cursor<?> cursor) {
        if (this.cursors == null){
            this.cursors = new ArrayList<>();
        }
        this.cursors.add(cursor);
    }


    /**
     * 关闭所有的游标
     */
    private void closeCursor() {
        if (this.cursors != null) {
            for (Cursor<?> cursor : this.cursors) {
                cursor.close();
            }
        }
    }


    public void rollBack() {
        this.executor.rollBack();
    }


    public void commit() {
        this.executor.commit();
    }


    public void close() {
        this.closeCursor();
        this.executor.close();
    }


    public Connection getConnection(){
        return this.executor.getConnection();
    }
}
